//------------------------------------------------------------------------------
// <copyright file="HeroPaginator.cs" company="Damian Antonowicz">
//       copyright © 2010 Damian Antonowicz
// </copyright>
// <author>Damian Antonowicz</author>
// <email>poczta@damianantonowicz.pl</email>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using DAnton.Utilities.Extensions;
using DotBeer.Business.Entities;
using DAnton.Utilities.Helpers;

namespace DotBeer.Business.Entities
{
    public class HeroPaginator : DocumentPaginator
    {
        #region private members

        PrintDialog printDialog = new PrintDialog();
        HeroPrintingSettings printingOptions;

        private Size pageSize;
        private Typeface typeface;
        private double fontSize;
        private double margin;
        private double skillNameMaxTextWidth = 280;
        private int pageCount, itemRowsPerPage;

        #endregion

        #region constructors

        public HeroPaginator(HeroPrintingSettings printingOptions)
        {
            this.printingOptions = printingOptions;
            this.typeface = new Typeface("Calibri");
            this.fontSize = 18;
            this.margin = 20;
            this.pageSize = new Size(this.printDialog.PrintableAreaWidth,
                                     this.printDialog.PrintableAreaHeight);

            PaginateData();
        }

        #endregion

        #region properties

        public override bool IsPageCountValid
        {
            get { return true; }
        }

        public override int PageCount
        {
            get { return pageCount; }
        }

        public override System.Windows.Size PageSize
        {
            get
            {
                return this.pageSize;
            }
            set
            {
                this.pageSize = value;
                PaginateData();
            }
        }

        public override IDocumentPaginatorSource Source
        {
            get { return null; }
        }

        #endregion

        #region public methods

        public override DocumentPage GetPage(int pageNumber)
        {
            DrawingVisual visual = new DrawingVisual();

            using (DrawingContext dc = visual.RenderOpen())
            {
                //draw invisible lines to stretch page visible in WindowPrint
                Point point = new Point(this.margin, this.margin);
                dc.DrawLine(new Pen(Brushes.Transparent, 1), point, new Point(pageSize.Width + margin, margin));
                dc.DrawLine(new Pen(Brushes.Transparent, 1), point, new Point(margin, pageSize.Height + margin));

                this.DrawHeader(dc);
                this.DrawItemBuild(dc, pageNumber);
                this.DrawSkillBuild(dc, pageNumber);
                //this.DrawComments(dc, pageNumber);
                this.DrawFoot(dc, pageNumber);
            }

            return new DocumentPage(visual);
        }

        #endregion

        #region private methods

        private void PaginateData()
        {
            int itemPageCount = GetItemPageCount();
            int skillPageCount = GetSkillPageCount();

            if (skillPageCount >= itemPageCount)
                this.pageCount = skillPageCount;
            else
                this.pageCount = itemPageCount;
        }

        private int GetItemPageCount()
        {
            itemRowsPerPage = GetItemRowsPerPage();
            int pageCount = 0;

            this.printingOptions.ItemBuilds.ForEach(build =>
                {
                    if (build.Print)
                        pageCount += this.GetPagesCountForItemBuild(build.Build);
                });

            return pageCount;
        }

        private int GetItemRowsPerPage()
        {
            return (976 / 40) - 1;
        }

        private int GetSkillPageCount()
        {
            int pageCount = 0;

            this.printingOptions.SkillBuilds.ForEach(build =>
                {
                    if (build.Print)
                        pageCount += this.GetPagesCountForSkillBuild(build.Build);
                });

            return pageCount;
        }

        private ItemBuild GetItemBuildFromPage(int pageNumber)
        {
            return this.GetBuildFromPage(this.printingOptions.ItemBuilds,
                                         pageNumber,
                                         build => this.GetPagesCountForItemBuild(build));
        }

        private SkillBuild GetSkillBuildFromPage(int pageNumber)
        {
            return this.GetBuildFromPage(this.printingOptions.SkillBuilds,
                                         pageNumber,
                                         build => this.GetPagesCountForSkillBuild(build));
        }

        private T GetBuildFromPage<T>(IEnumerable<BuildPrintingOptions<T>> builds,
                                      int pageNumber,
                                      Func<T, int> pageCountFunc)
        {
            int currentPage = 0;

            foreach (var build in builds)
            {
                if (build.Print)
                {
                    currentPage += pageCountFunc(build.Build);

                    if (pageNumber <= (currentPage - 1))
                        return build.Build;
                }
            }

            return default(T);
        }

        private int GetPagesCountForItemBuild(ItemBuild build)
        {
            return (int)Math.Ceiling((double)build.Items.Count / itemRowsPerPage);
        }

        private int GetPagesCountForSkillBuild(SkillBuild skillBuild)
        {
            FormattedText text;
            int currentPageCount = 1;
            double skillsHeight = 0;

            for (int index = 0; index < this.printingOptions.Hero.MaxNumberOfSkills; index++)
            {
                if (index >= skillBuild.Skills.Count)
                    break;

                text = GetFormattedText(skillBuild.Skills[index].Name);
                text.MaxTextWidth = 280;

                if (skillsHeight + text.Height > 976)
                {
                    currentPageCount++;
                    skillsHeight = 0;
                }

                skillsHeight += text.Height;
            }

            return currentPageCount;
        }

        private Dictionary<int, int> GetPagesNumberForItemBuild(ItemBuild itemBuild)
        {
            int currentPage = 0;

            foreach (var build in this.printingOptions.ItemBuilds)
            {
                if (build.Print)
                {
                    int pageCountForItemBuild = this.GetPagesCountForItemBuild(build.Build);

                    if (build.Build.Name == itemBuild.Name)
                    {
                        Dictionary<int, int> pages = new Dictionary<int, int>();

                        int buildPage = 1;
                        int count = currentPage + pageCountForItemBuild;

                        for (int index = currentPage; index <= count; index++)
                        {
                            pages.Add(index, buildPage);
                            buildPage++;
                        }

                        return pages;
                    }

                    currentPage += pageCountForItemBuild;
                }
            }

            return null;
        }

        private int GetFirstPageNumberForSkillBuild(SkillBuild skillBuild)
        {
            int currentPage = 0;
            int firstPage = 0;

            foreach (var build in this.printingOptions.SkillBuilds)
            {
                if (build.Print)
                {
                    currentPage += this.GetPagesCountForSkillBuild(build.Build);

                    if (build.Build.Name == skillBuild.Name)
                    {
                        firstPage = currentPage - this.GetPagesCountForSkillBuild(skillBuild);
                        break;
                    }
                }
            }

            return firstPage;
        }

        private void DrawHeader(DrawingContext dc)
        {
            Point point = new Point(this.margin, this.margin);
            FormattedText text;

            //draw hero image
            dc.DrawImage(new BitmapImage(new Uri(this.printingOptions.Hero.ImagePath)), new Rect(point, new Size(50, 50)));

            //draw hero name
            point.X = this.margin + 60;
            text = GetFormattedText(this.printingOptions.Hero.Name, 22);
            dc.DrawText(text, point);

            //draw author name
            point.Y += text.Height;
            text = GetFormattedText(String.Format("Author: {0}", printingOptions.Hero.Author));
            dc.DrawText(text, point);

            //draw DotBeer logo
            point.X = pageSize.Width - margin - 100;
            point.Y = margin;
            dc.DrawImage(new BitmapImage(new Uri(ApplicationPaths.DotBeerLogo)),
                         new Rect(point, new Size(98, 50)));
        }

        private void DrawItemBuild(DrawingContext dc, int pageNumber)
        {
            ItemBuild itemBuild = this.GetItemBuildFromPage(pageNumber);

            if (itemBuild != null)
            {
                Point point = new Point();
                FormattedText text;

                //draw Item Build
                //text = GetFormattedText("Item Build", 22);
                text = this.GetFormattedText(itemBuild.Name, 22);
                point.X = this.margin;
                point.Y = this.margin + 60;
                dc.DrawText(text, point);

                //draw underline
                point.Y = point.Y + text.Height;
                dc.DrawLine(new Pen(Brushes.Black, 2), point, new Point(margin + text.Width, point.Y));

                //draw items
                point.Y += 10;

                Dictionary<int, int> pages = this.GetPagesNumberForItemBuild(itemBuild);

                int endRow = itemRowsPerPage * (pages[pageNumber]);
                int startRow = endRow - itemRowsPerPage;

                if (startRow > 0)
                    startRow++;

                for (int index = startRow; index <= endRow; index++)
                {
                    if (index >= itemBuild.Items.Count)
                        break;

                    //draw image
                    point.X = margin;
                    double previousY = point.Y;

                    dc.DrawImage(new BitmapImage(new Uri(itemBuild.Items[index].ImagePath)),
                                 new Rect(point, new Size(35, 35)));

                    //draw name
                    point.Y += 5;
                    point.X = margin + 60;

                    text = GetFormattedText(itemBuild.Items[index].Name);
                    dc.DrawText(text, point);

                    point.Y = previousY;
                    point.Y += 40;
                }
            }
        }

        private void DrawSkillBuild(DrawingContext dc, int pageNumber)
        {
            SkillBuild skillBuild = this.GetSkillBuildFromPage(pageNumber);

            if (skillBuild != null)
            {
                Point point = new Point();
                FormattedText text;

                //calculate margin
                //
                //Skill Build
                text = this.GetFormattedText(skillBuild.Name, 22);
                text.MaxTextWidth = this.skillNameMaxTextWidth;

                double skillBuildNameHeight = text.Height;

                point.Y = margin + 60;
                point.X = margin + 453;

                //underline
                point.Y += text.Height;

                //draw skills
                point.Y += 5;

                double skillBeginingY = point.Y;
                double skillsHeight = 0;
                int currentPage = this.GetFirstPageNumberForSkillBuild(skillBuild);
                int maxNumberOfSkills = this.printingOptions.Hero.MaxNumberOfSkills;
                bool pageHasSkills = false;

                for (int index = 0; index < maxNumberOfSkills; index++)
                {
                    if (index >= skillBuild.Skills.Count)
                        break;

                    text = this.GetFormattedText(skillBuild.Skills[index].Name);
                    text.MaxTextWidth = 280;

                    //calculate current page
                    if (skillsHeight + text.Height > (976 - skillBuildNameHeight))
                    {
                        currentPage++;
                        skillsHeight = 0;
                        point.Y = skillBeginingY;
                    }

                    skillsHeight += text.Height;

                    if (currentPage == pageNumber)
                    {
                        pageHasSkills = true;

                        //draw skill level
                        text = GetFormattedText(String.Format("{0}. ", index + 1));
                        dc.DrawText(text, point);

                        //draw skill name
                        double textLevelWidth = text.Width + 5;
                        point.X += textLevelWidth;

                        text = this.GetFormattedText(skillBuild.Skills[index].Name);
                        text.MaxTextWidth = this.skillNameMaxTextWidth;
                        dc.DrawText(text, point);

                        point.X -= textLevelWidth;
                        point.Y += text.Height;
                    }
                }

                if (pageHasSkills)
                {
                    //draw Skill Build
                    text = this.GetFormattedText(skillBuild.Name, 22);
                    text.MaxTextWidth = this.skillNameMaxTextWidth;
                    point.Y = margin + 60;
                    point.X = margin + 453;
                    dc.DrawText(text, point);

                    //draw underline
                    point.Y = point.Y + text.Height;
                    dc.DrawLine(new Pen(Brushes.Black, 2), point, new Point(point.X + text.Width, point.Y));
                }
            }
        }

        private void DrawComments(DrawingContext dc, int pageNumber)
        {
            if (pageNumber == 0)
            {
                Point point = new Point();
                FormattedText text;

                point.X = this.margin + 453;
                point.Y = 681;

                text = GetFormattedText("Comments", 22);
                dc.DrawText(text, point);

                //draw underline
                point.Y = point.Y + text.Height;
                dc.DrawLine(new Pen(Brushes.Black, 2), point, new Point(point.X + text.Width, point.Y));

                //draw comments
                point.Y += 5;

                text = GetFormattedText(this.printingOptions.Hero.Comments);
                dc.DrawText(text, point);
            }
        }

        private void DrawFoot(DrawingContext dc, int pageNumber)
        {
            Point point = new Point();
            FormattedText text;

            //draw line
            point.X = margin;
            point.Y = pageSize.Height - 40;
            dc.DrawLine(new Pen(Brushes.Black, 2), point, new Point(pageSize.Width - margin, point.Y));

            //draw page x of x
            point.Y = pageSize.Height - 35;

            text = GetFormattedText(String.Format("page {0} of {1}", pageNumber + 1, pageCount));
            dc.DrawText(text, point);

            //draw time
            text = GetFormattedText(String.Format("printed at " + DateTime.Now.ToString("F", WMIHelper.GetOperatingSystemCultureInfo())));
            point.X = pageSize.Width - text.Width - margin;
            dc.DrawText(text, point);
        }

        private FormattedText GetFormattedText(string text)
        {
            return GetFormattedText(text, typeface, (int)fontSize);
        }

        private FormattedText GetFormattedText(string text, int size)
        {
            return GetFormattedText(text, typeface, size);
        }

        private FormattedText GetFormattedText(string text, Typeface typeface, int size)
        {
            return new FormattedText(
                text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                        typeface, size, Brushes.Black);
        }

        #endregion
    }
}
